﻿/*
	[ru]	Пример использования инстансинга и текстурного буфера.
*/


#include "Engine.h"
using namespace Engine;


#pragma pack(push,1)
struct TInstanceParams
{
	mat4		sModelMatrix;
	color4f		sColor;
};
#pragma pack(pop)


class CExample : public ICoreEventListener
{
private:
	CMaterialPtr		pMaterial;
	IProgramMaterial *	pMtrProg;
	CTextureBufferPtr	pTBO;
	CMeshPtr			pMesh;
	camera_t			sCamera;
	float				fLastTime;
	uint				uInstances;
	uint8				uTBOIndex;

	float				fMovingStep;
	float				fMouseK;
	bool				bIsCameraActive;

public:
	CExample(): fLastTime(0), uTBOIndex(0), uInstances(256), bIsCameraActive(true),
				fMovingStep(1.f), fMouseK(0.07f), pMtrProg(nullptr)
	{
	}

	~CExample()
	{
	}
	
	void Draw();
	void Process();

	void Moving();

	virtual void OnInit();
	virtual void OnFree();

	virtual void OnUpdate();

	virtual void Release()									{ delete this; }
	virtual e_win_event::type GetEvents()					{ return e_win_event::MESSAGE; }

	virtual void ProcessMessage(const TWindowEvent &sEvent);
	virtual void OnException()								{}
	
	static int s_RunEngine();
};



inline void CExample::Draw()
{
	RENDERGL()->ClearBuffers();
	
	MSTACK()->SetMatrix( sCamera.ProjMatrix(), e_transform::PROJECTION );
	MSTACK()->SetMatrix( sCamera.ViewMatrix(), e_transform::VIEW );
	MSTACK()->SetMatrix( sCamera.GetModelMatrix(), e_transform::MODEL );
	
	pMtrProg->Apply();
	pTBO->Bind( uTBOIndex );

	pMesh->DrawInstanced( uInstances );
	
	RENDERGL()->SwapBuffers();

	vec3	v_pos;
	RENDERGL()->GetWorldPos( INPUT()->GetMousePos().To<uvec2>(), v_pos );
	WINDOW()->SetCaption( string( (v_pos + sCamera.Position()).Length() ).cstr() );
}


inline void CExample::Process()
{
	INPUT()->Update();

	if ( INPUT()->IsKey(e_key::ESCAPE) )
		CORE()->QuitEngine();
	
	if ( INPUT()->IsMBClick(e_mouse::RIGHT) )
		bIsCameraActive = !bIsCameraActive;

	if ( bIsCameraActive )
	{
		Moving();
		INPUT()->SetParams( e_input_flags::HIDE_CURSOR | e_input_flags::EXCLUSIVE );
	}
	else
		INPUT()->SetParams( e_input_flags::UNKNOWN );

	INPUT()->Reset();
}


inline void CExample::Moving()
{
	vec3	v_move;
	vec3	v_rotate = vec3( INPUT()->GetMouseDelta() * fMouseK );

	if ( INPUT()->IsKey(e_key::W) )				v_move.x =  fMovingStep;	else
	if ( INPUT()->IsKey(e_key::S) )				v_move.x = -fMovingStep;
	if ( INPUT()->IsKey(e_key::D) )				v_move.y =  fMovingStep;	else
	if ( INPUT()->IsKey(e_key::A) )				v_move.y = -fMovingStep;
	if ( INPUT()->IsKey(e_key::LSHIFT) )			v_move.z =  fMovingStep;	else
	if ( INPUT()->IsKey(e_key::SPACE) )			v_move.z = -fMovingStep;	

	v_rotate /= sCamera.GetZoom();

	sCamera.RotateFPS( v_rotate.xy() );
	sCamera.MoveFPSFree( v_move );

	int		i_wheel	= INPUT()->GetDeltaWheel();

	if ( i_wheel != 0 )
	{
		sCamera.Increase( sCamera.GetZoom() + float(i_wheel) * 0.02f );
	}
	else
	if ( INPUT()->IsMBClick( e_mouse::MIDDLE ) )
		sCamera.Increase( 1.f );

	INPUT()->CursorToCenter();
}


void CExample::OnInit()
{
	bool						ret			= true;
	IMaterial::progmtr_array_t	a_mtrs;
	
	IRenderTechnique	*p_tech = nullptr;

	ret &= RENDER()->GetRTManager()->CreateTechnique( p_tech, e_technique::FORWARD_LIGHTING );
	RENDER()->SetRenderTechnique( p_tech );

	ret &= RESMAN()->Load( e_resource::MATERIAL,				pMaterial,	"instancing.ocf" );
	ret &= RESMAN()->Create( e_resource::TEXTURE_BUFFER,		pTBO );
	ret &= RESMAN()->Create( e_resource::MESH,				pMesh );

	TMultiMesh	s_geom;

	if ( ret && Geometry::CreateCubeCM( s_geom, 15.f ) )
	{
		ret &= pMesh->Create( s_geom, e_mesh_flags::STATIC );
	}
	
	if ( ret )
	{
		CBaseProgramShellPtr	p_prog;

		pMaterial->GetUniqueMaterials( a_mtrs );

		if ( a_mtrs.Empty() ) {
			WARNING( "" );
			return;
		}

		pMtrProg = a_mtrs[0];
		pMtrProg->GetProgram( p_prog );

		uTBOIndex = p_prog->GetTextureStage( e_texture::INSTANCE_DATA );
		ret &= uTBOIndex != uint8(-1);
	}


	// create TBO
	uint	num_x = 16;
	uint	num_y = 16;
	uint	num_z = 16;

	uInstances = num_x * num_y * num_z;

	ret &= pTBO->Create();
	ret &= pTBO->SetDynamic( e_tex_format::CIF_RGBA32F, sizeof(TInstanceParams), uInstances * sizeof(TInstanceParams) );


	// create matrices
	float	f_scale = 30.f;
	float	f_min_x = float(num_x) * 0.5f;
	float	f_min_y = float(num_y) * 0.5f;
	float	f_min_z = float(num_z) * 0.5f;

	for (uint x = 0; x < num_x; ++x)
	for (uint y = 0; y < num_y; ++y)
	for (uint z = 0; z < num_z; ++z)
	{
		TInstanceParams	s_params;
		float			tmp;
		vec3			pos	  = vec3( x*f_scale - f_min_x, y*f_scale - f_min_y, z*f_scale - f_min_z );

		s_params.sModelMatrix = mat4::Translate( pos ) *
								mat4::RotationX( DTR( RandClamp< TRandomEngine >( tmp ) * 360.f ) ) *
								mat4::RotationZ( DTR( RandClamp< TRandomEngine >( tmp ) * 360.f ) );
		s_params.sColor		  = vec4( Color::Random<float,3>(), 1.f );

		pTBO->PushBackElem( &s_params, sizeof(s_params) );
	}

	ret &= pTBO->UpdateBuffer();

	RENDERGL()->SetClearColor( color4f(0.0f, 0.0f, 0.1f, 1.f) );

	SMAN()->Enable( e_gl_state::DEPTH_TEST );
	
	sCamera.Create( transform_t( vec3(-200.f, -200.f, -1300.f), quat() ),
		OSAPI()->GetRealViewportSize( RENDER()->GetViewport()->Size() ), 0.4f * 0.6f, vec2(1.f, 1000.f) );

	ASSERT( ret );
}


void CExample::OnFree()
{
}


void CExample::OnUpdate()
{
	CORE()->Update();
	Draw();
	
	fLastTime += CORE()->GetUpdateTimeDelta();

	if ( fLastTime > 1.f/60.f )
	{
		fLastTime = 0.f;
		Process();
	}
}


void CExample::ProcessMessage(const TWindowEvent &sEvent)
{
	if ( sEvent.sEvent.sMsg.eMsg == TMessage::RESIZE ||
		 sEvent.sEvent.sMsg.eMsg == TMessage::MAXIMIZED ||
		 sEvent.sEvent.sMsg.eMsg == TMessage::MINIMIZED )
	{
		sCamera.Resize( OSAPI()->GetRealViewportSize( RENDER()->GetViewport()->Size() ), 0.4f );
	}
}


int CExample::s_RunEngine()
{
	CExample	*		p_example			= new CExample();
	TEngInitParams		s_init_params;
	e_log_output::bits	e_log_bits			= e_log_output::LOG_FILE_BIT
											DEBUG_ONLY( | e_log_output::MESSAGE_BIT )
											;
	
	s_init_params.eFlags					= e_eng_init_flags::OPENGL_4_2
											| e_eng_init_flags::CORE_PROFILE
											| e_eng_init_flags::EXTENDED_CONTEXT
											//| e_eng_init_flags::DEBUG_MODE
											//| e_eng_init_flags::STREAMED_LOAD
											//| e_eng_init_flags::STEREO
											//| e_eng_init_flags::ES2_PROFILE
											;
	s_init_params.sApplicationName			= "Geometry Instancing and Texture Buffer Sample";
	s_init_params.sClassName				= "";
	s_init_params.pHandle					= nullptr;
	s_init_params.pIcon						= nullptr;

	s_init_params.sAudioParams.sAudioDevice	= "";

	s_init_params.sRenInit.uColorDepth		= 32;
	s_init_params.sRenInit.uZBufferDepth	= 24;
	s_init_params.sRenInit.uStencilDepth	= 0;
	s_init_params.sRenInit.uColorSamples	= 0;
	s_init_params.sRenInit.uCSAASamples		= 0;
	s_init_params.sRenInit.uMSAASamples		= 0;
	s_init_params.sRenInit.uPixelFormat		= 0;

	s_init_params.sRenParams.bFullScreen	= false;
	s_init_params.sRenParams.bVSync			= true;
	s_init_params.sRenParams.eMode			= e_window_mode::ALLOW_SIZING | e_window_mode::CENTERED;
	s_init_params.sRenParams.sWinPos		= ivec2( 0, 0 );
	s_init_params.sRenParams.sWinSize		= ivec2( 640, 480 );
	
	s_init_params.sExtParams.uLogLevel		= 15;
	s_init_params.sExtParams.eLogFilter		= e_log_msg::FILTER_ALL;
	s_init_params.sExtParams.eResManFlags	= e_resman_flags::FREE_NON_REF | e_resman_flags::SET_DEFAUL_RES;
	s_init_params.sExtParams.sResDirectory	= "data";
	s_init_params.uLoadThreads				= 0;
	s_init_params.uRenderThreads			= 0;
	
	if ( !RunEngine( p_example, s_init_params, e_log_bits, "log.html", "data\\config\\settings.ocf" ) )
		return 1;

	return 0;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	return CExample::s_RunEngine();
}